On this page

Skip to content

DateTime Time Zone Issues and Solutions in Entity Framework

TLDR

  • The Kind property of DateTime (Local, Utc, Unspecified) affects the conversion results of ToLocalTime() and ToUniversalTime(). Improper handling will lead to time deviations.
  • When reading DateTime from a database, EF Core defaults the Kind to Unspecified because database columns do not store time zone information, causing incorrect time display on the frontend.
  • Solutions should be handled at the data access layer rather than appending a Z character on the frontend.
  • It is recommended to use EF Core's ValueConverter or the .NET 6 ConfigureConventions() global setting to automatically force the retrieved time to be marked as Utc.

DateTime Time Zone Format Issues

When developers are unaware of the state of DateTime.Kind, calling ToLocalTime() or ToUniversalTime() directly can result in unexpected time offsets.

When this issue occurs: When the system mixes DateTime objects from different sources without unified checking or conversion of the Kind property.

  • Kind is Local: Calling ToLocalTime() does not change the time.
  • Kind is Utc: Calling ToUniversalTime() does not change the time.
  • Kind is Unspecified: The system assumes the time is UTC and converts it to local time (adding an offset), or assumes it is local time and converts it to UTC (subtracting an offset).

To avoid such issues, it is recommended to follow the practices of frameworks like ABP.IO by defining a unified IClock interface to standardize time conversion and ensure Kind meets expectations.

Entity Framework DateTime Time Zone Issues

When using database types that do not contain time zones, such as datetime or datetime2, the Kind of data retrieved by EF Core defaults to Unspecified. This causes the time string to lack the Z suffix when serialized to the frontend, leading to discrepancies between the displayed time and the expected time.

When this issue occurs: When using Code First or reverse engineering to generate Entities, and the database columns do not contain time zone information.

Solution: Use ValueConverter for Automatic Conversion

Through ValueConverter, you can ensure the time is UTC before writing to the database and force the Kind to Utc upon reading.

Define the converter class:

csharp
public class UtcDateTimeValueConverter : ValueConverter<DateTime, DateTime> {
    public UtcDateTimeValueConverter()
        : base(v => ToDb(v), v => FromDb(v)) {
    }

    private static DateTime ToDb(DateTime dateTime) {
        return dateTime.Kind == DateTimeKind.Utc ? dateTime : dateTime.ToUniversalTime();
    }

    private static DateTime FromDb(DateTime dateTime) {
        return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
    }
}

Global Configuration

To avoid configuring properties one by one, it is recommended to use ConfigureConventions in DbContext (.NET 6 and above) for global configuration:

csharp
public partial class MyDbContext : DbContext {
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) {
        ArgumentNullException.ThrowIfNull(configurationBuilder);

        configurationBuilder.Properties<DateTime>().HaveConversion<UtcDateTimeValueConverter>();
    }
}

If ConfigureConventions cannot be used, you can iterate through all properties in OnModelCreating and apply the converter:

csharp
partial void OnModelCreatingPartial(ModelBuilder modelBuilder) {
    foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) {
        foreach (IMutableProperty property in entityType.GetProperties()) {
            if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) {
                property.SetValueConverter(typeof(UtcDateTimeValueConverter));
            }
        }
    }
}

Change Log

  • 2024-08-15 Initial document creation.